/**
 * @author        Nicolas Bottarini <nicolasbottarini@gmail.com>, 404 <http://www.proyecto404.com>
 * @version       $Rev: 3 $
 * @lastrevision  $Date: 2011-09-12 16:45:37 +0000 (Mon, 12 Sep 2011) $	 
 */
package com.proyecto404.core.collections {
	import com.proyecto404.core.BaseObject;
	import com.proyecto404.core.Check;
	import com.proyecto404.core.exception.InvalidOperationException;
	import com.proyecto404.core.exception.NotImplementedException;
	
	public class Stack extends BaseObject implements IStack {
		private var _data:Array;
		private var _pointer:int;
		
		public function Stack(...rest) {
			var array:Array;
			if (rest.length == 1 && rest[0] is Array){
				array = rest[0];
			} else {
				array = rest;
			}
			
			_data = new Array();
			_pointer = -1;

			if (array != null) {
				addFromArray(array);
			}
		}
		
		public function withValue(value:*):IStack {
			add(value);
			return this;
		}
		
		public function withValuesFrom(values:*):IStack {
			if (values is Array) {
				addFromArray(values as Array);
			} else if (values is ICollection) {
				addFromCollection(values as ICollection);
			} else {
				throw new InvalidOperationException("Object argument must be an array or a collection");
			}
			
			return this;
		}
		
		public function count():int {
			return _data.length;
		}
		
		public function isEmpty():Boolean {
			return count() == 0;
		}
		
		public function contains(value:*):Boolean {
			for(var i:int = 0; i < count(); i++) {
				if(areEquals(_data[i], value)) {
					return true;
				}
			}
			
			return false;
		}
		
		public function clear():void {
			_data = new Array();
			_pointer = -1;
		}
		
		public function toList():IList {
			return new List(toArray());
		}
		
		public function toArray():Array {
			return _data.reverse();
		}
		
		public function toSet():ISet {
			return new Set(toArray());
		}
		
		public function push(value:*):void {
			_data.push(value);
			_pointer++;
		}
		
		public function top():* {
			Check.require(count() > 0, "Empty Stack");
			return _data[_pointer];
		}

		public function pop():* {
			Check.require(count() > 0, "Empty Stack");
			_pointer--;
			return _data.pop();
		}

		public function add(value:*):void {
			push(value);
		}
		
		public function remove(value:*):void {
			throw new InvalidOperationException("Cannot remove elements from Stack");
		}
		
		public function removeFromArray(array:Array):void {
			Check.require(array is Array, "Argument must be an array");
			for(var i:int = 0; i < array.length; i++ ) {
				remove(array[i]);
			}
		}
		
		public function removeFromCollection(collection:ICollection):void {
			var it:IIterator = collection.getIterator();
			while (it.hasNext()) {
				remove(it.next());
			}
		}
		public function addFromArray(array:Array):void {
			Check.require(array is Array, "Argument must be an array");
			for(var i:int = 0; i < array.length; i++ ) {
				add(array[i]);
			}
		}
		
		public function addFromCollection(collection:ICollection):void {
			var it:IIterator = collection.getIterator();
			while (it.hasNext()) {
				add(it.next());
			}
		}
		
		public function asReadOnly():ICollection {
			return new ReadOnlyStack(this);
		}
		
		public function getIterator():IIterator {
			return new ArrayIterator(toArray());
		}
		
		public function implode(separator:String = ","):String {
			var string:String = "";
			var first:Boolean = true;
			for(var it:IIterator = getIterator() ; it.hasNext() ; ) {
				if (!first) {
					string += separator;
				} else {
					first = false;
				}
				
				string += valueToString(it.next());
			}
			
			return string;
		}
		
		public override function toString():String {
			return "Stack{" + implode() + "}";
		}
		
		public override function equals(value:*):Boolean {
			if(!(value is Stack)) return false;
			var other:Stack = Stack(value);
			
			if(count() != other.count()) return false;
			
			for(var i:int = 0; i < count(); i++) {
				if(!areEquals(_data[i], other._data[i])) return false;
			}
			
			return true;
		}
		
		public function collect(criteria:Function):ICollection {
			var newStack:Stack = new Stack();
			var it:IIterator = getIterator();
			while(it.hasNext()) {
				var item:* = it.next();
				if (criteria.call(this, item)) {
					newStack.push(item);
				}
			}
			return new Stack(newStack._data.reverse());
		}
		
		public function forEach(callback:Function):void {
			var it:IIterator = getIterator();
			while(it.hasNext()) {
				callback.call(this, it.next());
			}
		}
		
		public function map(convert:Function):ICollection {
			var newStack:Stack = new Stack();
			var it:IIterator = getIterator();
			while(it.hasNext()) {
				var item:* = it.next();
				newStack.push(convert.call(this, item));
			}
			return new Stack(newStack._data.reverse());
		}
		
		public function all(criteria:Function):Boolean {
			var it:IIterator = getIterator();
			while(it.hasNext()) {
				var item:* = it.next();
				if (!criteria.call(this, item)) {
					return false;
				}
			}
			return true;
		}
		
		public function any(criteria:Function):Boolean {
			var it:IIterator = getIterator();
			while(it.hasNext()) {
				var item:* = it.next();
				if (criteria.call(this, item)) {
					return true;
				}
			}
			return false;
		}
	}
}